/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* SCRMGR Generic Data editor */

#include <stdio.h>
#include <getopt.h>
#include <setjmp.h>
#include <unistd.h>
#include "smlib.h"
#include "smio.h"
#include "smdata.def"

CHAR *smdata_rcsid(void) {
    return("$Id: smdata.c,v 1.6 2008/02/19 13:15:50 bhepple Exp $");
}

static struct {
    CHAR	*rev_string;
    int		force_memory;
    int		force_file;
    CHAR 	last_scr_filename[MAXPATHLEN + 1];
} global;

jmp_buf		NoMoreHeap;

static CHAR *usage_msg[] = {
"Edits data files according to SCRMGR screens.",
"",
#ifdef SM_TURBOC
"Backups of files are preserved in \"filename.~tx\"",
#else
"Backups of files are preserved in \"filename~\".",
#endif
"",
"-m         : force temporary storage into memory (heap)",
"-f         : force temporary storage onto disc",
"screenfile : use screen file to define the data structure.",
"screen     : only use the screen named \"screen-name\" from \"screenfile\".",
"datafile   : override data file(s) specified in \"screenfile\".",
NULL
};

#if !READ_DAT_AT_RUNTIME
static CHAR *smdata_dat[] = {
#include "smdata.dat"
NULL,
};
#endif

static void usage()
{
    CHAR **c;
    
    fprintf(stderr, "%s of %s.\n", global.rev_string, sm_program_name);
    fprintf(stderr, 
"Usage: %s screenfile[:screen-name] [-m|f] [datafile [datafile...]]\n", sm_program_name);
    
    c = usage_msg;
    while (*c) {
        fputs(*c, stderr);
        fputc('\n', stderr);
        c++;
    }
    exit(2);
}

void sm_error_exit ARG1(CHAR *, msg)
{
    sm_term();
    puts(msg);
    exit(1);
}

void re_fail ARG2(char *, msg, char, op)
{
    sm_message(msg);
}

/* Exported to the SCRMGR package */
f_ptr sm_get_field_trigger ARG1(SM_FIELD, field)
{
    return(NULL);
}

static int dirty_data()
{
    SM_SCREEN	*screenp;

    for (screenp = sm_first_scr(sm_user_screens()); 
	 screenp && *screenp; 
	 screenp = sm_next_scr(screenp))
	if (smdb_file_changed(*screenp))
	    return(1);
    return(0);
}

CALLBACK(data_edt_done)
{
    return(command);
}	

static void data_edt ARG1(SM_SCREEN, screen)
{
    sm_add_callback(data_edt_done, NULL);
    sm_setup_edit(SM_BASE_WINDOW, screen, MAIN_KEYS, NULL, NULL, NULL);
}

static int data_open ARG1(SM_SCREEN, screen)
{
    if (smdb_read_file(screen->filename, screen, sm_user_screens()->untabify))
        return(SM_ERROR);
    else
	return(0);
}

/* Note that this stores the filename on the screen data structure for
   future use.
*/
static int store_file ARG3(CHAR *, filename, SM_SCREEN, screen, int, emergency)
{
    if (smdb_write_file(filename, screen, emergency) == 0) {
	SM_ASSIGN(screen->filename, filename);
	return(0);
    } else
	return(SM_ERROR);
}

CALLBACK(do_store_files_as)
{
    CHAR	*filename, *scrname;
    CHAR	*buf = alloca(SM_MAXREC);
    SM_SCREEN	*screenp = sm_first_scr(sm_user_screens()), screen;

    if (command == MAIN_KEYS) {
	smdb_seek(store_files_scr, (INDEX) 0);
	while ((buf = smdb_get_current(store_files_scr))) {
	    sm_data_to_screen(store_files_scr, buf, 0);
	    if (*sm_fld_get(store_files_scr, "save") == 'Y') {
		filename = sm_fld_get(store_files_scr, "filename");
		scrname  = sm_fld_get(store_files_scr, "screen_name");
		if (*scrname)
		    screen = sm_scr_open(sm_user_screens(), scrname);
		else { /* In case it's an 'anonymous' screen */
		    screen = screenp? *screenp: NULL;
		    screenp = sm_next_scr(screenp);
		}
		if (*filename)
		    store_file(filename, screen, 0);
	    }
	    smdb_read_next(store_files_scr);
	}
    }
    smdb_delete_data(store_files_scr);
    return(command);
}

static int store_files_lambda ARG2(int, command, SM_SCREEN, screen)
{
    INDEX	index;
    CHAR	*buf;

    switch (command) {
    case MAIN_KEYS:
	break;
    case SAVE_ALL:
    case SAVE_NONE:
	index = smdb_enq_position(store_files_scr);
	smdb_seek(store_files_scr, (INDEX) 0);
	while ((buf = smdb_get_current(store_files_scr))) {
	    sm_data_to_screen(store_files_scr, buf, 0);
	    sm_fld_set(sm_fld_open(store_files_scr, "save"),
		       (command == SAVE_ALL?"Y":"N"));
	    sm_screen_to_data(buf, store_files_scr);
	    smdb_set_current(store_files_scr, buf);
	    smdb_read_next(store_files_scr);
	}
	smdb_seek(store_files_scr, index);
	command = 0;
	break;
    case SM_QUIT_C:
	break;
    default:
	break;
    }
    return(command);
}

static void setup_store_files_as()
{
    SM_SCREEN		*screenp;
    CHAR		buf[SM_MAXREC];

    sm_add_callback(do_store_files_as, NULL);

    for (screenp = sm_first_scr(sm_user_screens()); 
	 screenp && *screenp; 
	 screenp = sm_next_scr(screenp)) {
	sprintf(buf, "%s%c%s%c%s%c%s%c",
		(*screenp)->screen_name, 		
		    store_files_scr->parent->separator,
		(*screenp)->filename? (*screenp)->filename: "",
		    store_files_scr->parent->separator,
		(smdb_file_changed(*screenp)?"Y":"N"), 	
		    store_files_scr->parent->separator,
		(smdb_file_changed(*screenp)?"Y":"N"), 	
		    store_files_scr->parent->separator);
	smdb_write(store_files_scr, buf);
    }
    smdb_seek(store_files_scr, (INDEX) 0);
    sm_setup_edit(SM_BASE_WINDOW, 
		  store_files_scr, 
		  SM_QUIT_C, 
		  store_files_lambda,
		  store_files_skey,
		  NULL);
}

/* This saves all the files without prompting unless there are files 
   needing saving with no filename - then save_files_as() is used and 
   -1 is returned to indicate that a callback was placed on the stack.

   Otherwise the number of files not stored due to errors is returned
*/
static int store_files ARG1(int, emergency)
{
    SM_SCREEN	*screenp;
    int		errors = 0;

    for (screenp = sm_first_scr(sm_user_screens()); 
	 screenp && *screenp; 
	 screenp = sm_next_scr(screenp))
	if (smdb_file_changed(*screenp) && !((*screenp)->filename)) {
	    setup_store_files_as();
	    return(-1);
	}

    for (screenp = sm_first_scr(sm_user_screens()); 
	 screenp && *screenp; 
	 screenp = sm_next_scr(screenp))
	if (smdb_file_changed(*screenp))
	    errors += store_file((*screenp)->filename, *screenp, emergency);
    return(errors);
}

CALLBACK(do_exit)
{
    if (dirty_data() &&
        (sm_confirm("Really exit and lose changes?") != SM_YES_C))
        return(SM_POPCALLBACK);
    if (command != SM_QUIT_C)
	while (sm_destroy_callback())
	    ; /* To kill smdata_main_loop */
    return(SM_POPCALLBACK);
}

struct load_s {
    f_ptr		destructor;
    SM_SCREEN		screen;
};

static int load_destructor ARG1(struct load_s *, object)
{
    free(object);
    return(0);
}

CALLBACK(load_file_done)
{
    struct load_s *load = (struct load_s *) callback_data;

    if (command == SM_YES_C) {
	sm_message("Loading ...");
    	SM_ASSIGN(load->screen->filename, sm_get_prompt_value());
    	if (data_open(load->screen) == 0)
            sm_message("");
    }
    return(command);
}

CALLBACK(smdata_main_loop)
{
    SM_SCREEN		*screenp, screen;
    struct load_s	*load;
    int			errors;
    
    screenp = sm_first_scr(sm_user_screens());
    screen = screenp? *screenp: NULL;

    switch(command) {
    case SM_LOAD_FILE_C:
	/* This only applies to the first file ... */
	if (!dirty_data() || (sm_confirm("Lose changes?") == SM_YES_C)) {
	    CHAR	pwd[MAXPATHLEN + 1];

	    load = (struct load_s *) xmalloc((INDEX) sizeof(struct load_s));
	    load->destructor = load_destructor;
	    load->screen = screen;
	    smdb_delete_data(screen);
	    sm_add_callback(load_file_done, load);
	    GETCWD(pwd, MAXPATHLEN);
	    sm_setup_prompt("Enter filename to read (current directory is:)", 
			    pwd,
			    screen? screen->filename: NULL,
			    NULL);
	}
	return(0);
	
    case SM_STORE_FILE_C:
	errors = store_files(0);
	if (errors == -1)
	    return(0);
	break;

    case SM_STORE_FILE_AS_C:
	setup_store_files_as();
	return(0);
	
    case SM_CLEAR_FILE_C:
	if (!dirty_data() || (sm_confirm("Clear data file?") == SM_YES_C)) {
	    smdb_delete_data(screen);
	    sm_message("Data files cleared");
	}
	break;
	
    case EDIT_DATA:
	data_edt(screen);
	return(0);
	
    case SM_SELECT_SCREEN_C:
	{
	    CHAR 	*buf = alloca(200);

	    smio_get_argument(buf);
	    if (*buf) {
		SM_SCREEN s = sm_scr_open(sm_user_screens(), buf);
		if (s) {
		    screen = s;
		    data_edt(screen);
		}
	    }
	}
	return(0);
	
    case SM_EXIT_C:
	if (dirty_data()) {
	    sm_add_callback(do_exit, NULL);
	    setup_store_files_as();
	    return(0);
	}
    case SM_NO_SAVE_EXIT_C:
	return(command);
	break;

    default: /* Probably a return from one of the sub routines */
	break;
    }

    sm_fld_set(sm_fld_open(main_screen, "changed"), 
	       dirty_data()? "Y": "N");

    sm_setup_scr(SM_BASE_WINDOW, main_screen, NULL, NULL, 0, 0, 0);
    return(0);
}

int main ARG2(int, argc, CHAR **, argv)
{
    CHAR		*screen_to_use = NULL;
    int			c;
    SM_SCREEN		*screenp, screen;
    struct store_s	*store = NULL;
    SM_FILE		screenset;

    /* Temporary back door */
    if (setjmp(NoMoreHeap)) {
	alloca(0);
	sm_error_exit(sm_mem_error);
    }

    global.rev_string = "$Revision: 1.6 $";
    global.force_memory = FALSE;
    global.force_file = FALSE;

    sm_pre_init(argc, argv);
    sm_init(1, argc, argv);

    while ((c = getopt(argc, argv, "hmf?")) != EOF)
        switch (c) {
	case 'm':
	    global.force_memory = TRUE;
	    global.force_file = FALSE;
	case 'f':
	    global.force_memory = FALSE;
	    global.force_file = TRUE;
	    break;
        default:  
	    /* Unfortunately, if we've entered X in sm_init, then any error 
	       messages disappear */
            sm_term();
	    usage(); 
	    break;
        }
    
    if ((optind >= argc) || (strlen(argv[optind]) == 0))
	usage();
    else { /* Use first argument as screen file */
	if ((screen_to_use = strchr(argv[optind], ':')) != NULL)
	    *screen_to_use++ = 0;
	strcpy(global.last_scr_filename, argv[optind++]);
    }

#ifdef SM_DEBUG
    smio_flushinp();
#endif

#if READ_DAT_AT_RUNTIME
    {
	CHAR	buf[MAXPATHLEN + 1];

	buf[0] = 0;
#if NEEDS_PATH_FOR_PROGS
	strcpy(buf, sm_program_directory);
#endif
	strcat(buf, "smdata.dat");
	if (sm_fopen(sm_scrmgr_screens(), buf) == SM_ERROR)
	    sm_error_exit("Error reading smdata.dat!");
    }
#else
    if (sm_dbopen(sm_scrmgr_screens(), smdata_dat) == SM_ERROR)
	sm_error_exit("Error reading internal data block!");
#endif

    screenset = sm_scrmgr_screens();
#include "smdata.opn"

    /* Go get screenfile */
    if (sm_fopen(sm_user_screens(), global.last_scr_filename) == SM_ERROR) {
	sm_error("Error reading the screen file.");
	sm_term();
	exit(1);
    }

    sm_fld_set(sm_fld_open(main_screen, "screen_name_display"), 
	       global.last_scr_filename);

    sm_fld_set(sm_fld_open(main_screen, "changed"), "N");

    /* if a screen was given, delete all but that screen */
    for (screenp = sm_first_scr(sm_user_screens());
	 screen_to_use && screenp && *screenp;) {

	if (strcmp((*screenp)->screen_name, screen_to_use)) {
	    sm_destroy_scr(screenp);
	    screenp = sm_first_scr(sm_user_screens());
	} else {
	    SM_FIELD	*fp;

	    for (fp = sm_first_fld(*screenp);
		 fp && *fp;
		 fp = sm_next_fld(fp))
		(*fp)->master_screen_name = NULL; 
	        /* to allow simple edits to be done */

	    screenp = sm_next_scr(screenp);
	}
    }

    screenp = sm_first_scr(sm_user_screens());
    if ((screenp == NULL) || (*screenp == NULL)) {
	if (screen_to_use)
	    sm_error_exit("No such screen in that file!");
	else
	    sm_error_exit("No screens in that file!");
    }
    
    /* Attach any data in filenames passed as arguments to consecutive
       screens */
    for (screenp = sm_first_scr(sm_user_screens());
	 screenp && *screenp && (optind < argc); 
	 screenp = sm_next_scr(screenp), optind++) {

	screen = *screenp;
	SM_ASSIGN(screen->filename, argv[optind]);
	if (global.force_memory)
	    screen->temp_storage = 'M';
	else if (global.force_file)
	    screen->temp_storage = 'F';
        data_open(screen);
    }

    /* Now open any files mentioned as defaults in the screenfile
       definition provided data has not already been read in for that file*/
    for (screenp = sm_first_scr(sm_user_screens());
	 screenp && *screenp; 
	 screenp = sm_next_scr(screenp)) {

	screen = *screenp;
	if (screen->filename) {
	    if (screen->data == NULL)
		data_open(screen);
	} else if (screen->temp_storage == 'F')
	    smdb_read_file(NULL, screen, sm_user_screens()->untabify);
    }

    /* Now set default screen */
    screenp = sm_first_scr(sm_user_screens());
    screen = screenp? *screenp: NULL;

    /* Back door for when we run out of heap - horrible structure, but
       what can one do ?*/

    if (setjmp(NoMoreHeap)) {
	alloca(0);

	/* Prevent infinite loop */
	if (setjmp(NoMoreHeap)) {
	    alloca(0);
	    sm_error_exit(sm_mem_error);
	}

	sm_message(sm_mem_error);

	if (setjmp(NoMoreHeap)) {
	    /* Oh boy - really serious! */
	    alloca(0);
	    sm_error_exit("Ran out of heap - file(s) were not saved! Sorry!");
	}

	store_files(1);
	free(store);
	alloca(0);

	if (setjmp(NoMoreHeap) == 0)
	    sm_term();
	exit(1);
    }

    /*************/
    /* Main loop */
    /*************/

    sm_message(global.rev_string);
    sm_push_command(EDIT_DATA);
    sm_setup_scr(SM_BASE_WINDOW, main_screen, NULL, NULL, 0, 0, 0);
    sm_add_callback(smdata_main_loop, NULL);	
    sm_main_loop();
    sm_term();
    exit(0);
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
